using Newtonsoft.Json;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Link_eLab.Helper.CopyObject
{
    /// <summary>
    /// 复制对象属性（可以跨类型），这要花一点性能代价，但是写代码很快、不会遗漏出错
    /// 目标对象必须是带有get set的public属性否则不能识别，源对象可以没有set只有get
    /// </summary>
    public static class CopyObject
    {
        /// <summary>
        /// 记得加await或.Result
        ///
        /// 暴力复制同类型的对象到新对象
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="source">源对象</param>
        /// <returns>新对象</returns>
        public static Task<T> Clone<T>(T source)
        {
            try
            {
                var serialized = JsonConvert.SerializeObject(source);
                return Task.FromResult(JsonConvert.DeserializeObject<T>(serialized));
            }
            catch
            {
#if DEBUG
                Debugger.Break();
#endif
                throw;
            }
        }

        /// <summary>
        /// 记得加await或.Result
        ///
        /// 警告：此方法使用不当时会丢失信息
        /// 暴力跨类型转换到新对象
        /// 仅名称、类型完全相同的属性会被保留
        ///
        /// 坑1:
        /// 当源类型上“可为空”的空值属性被该方法Cast到“不可为空”的目标类型属性上时会报错
        /// 谨慎使用或改用较慢的变体T1 source, T2 target
        /// </summary>
        /// <typeparam name="T1">源类型，或用"dynamic"即可免去显式指定</typeparam>
        /// <typeparam name="T2">目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <returns>目标对象</returns>
        public static Task<T2> Cast<T1, T2>(T1 source)
        {
            try
            {
                var serialized = JsonConvert.SerializeObject(source);
                return Task.FromResult(JsonConvert.DeserializeObject<T2>(serialized));
            }
            catch
            {
#if DEBUG
                Debugger.Break();
#endif
                throw;
            }
        }

        /// <summary>
        /// 记得加await或.Result
        ///
        /// 警告：此方法使用不当时会丢失信息
        /// 暴力跨类型转换到已存在对象
        /// 仅名称、类型完全相同的属性会被处理，不符合条件的属性会保留在目标对象上
        /// </summary>
        /// <typeparam name="T1">源类型，或用"dynamic"即可免去显式指定</typeparam>
        /// <typeparam name="T2">目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <param name="target">目标对象</param>
        /// <returns>JSON结果集</returns>
        public static Task<T2> Cast<T1, T2>(T1 source, T2 target)
        {
            try
            {
                var sourceProperties = source.GetType().GetProperties();
                var targetProperties = target.GetType().GetProperties();

                // Parallel 循环可利用多线程加速处理
                Parallel.ForEach(sourceProperties, sourceProperty =>
                {
                    Parallel.ForEach(targetProperties, targetProperty =>
                    {
                        bool sameName = sourceProperty.Name == targetProperty.Name;
                        bool sameType = sourceProperty.PropertyType == targetProperty.PropertyType;

                        if (sameName && sameType)
                        {
                            targetProperty.SetValue(target, sourceProperty.GetValue(source));
                        }
                    });
                });

                return Task.FromResult(target);
            }
            catch
            {
#if DEBUG
                Debugger.Break();
#endif
                throw;
            }
        }

        /// <summary>
        /// 记得加await或.Result
        ///
        /// 警告：此方法使用不当时会丢失信息
        /// 暴力跨类型转换到已存在对象
        /// 仅名称、类型完全相同的属性会被处理，不符合的条件属性会保留在目标对象上
        /// 按值传递，单线程，较慢，当对象不支持被“整个赋值”时可使用
        /// </summary>
        /// <typeparam name="T1">源类型，或用"dynamic"即可免去显式指定</typeparam>
        /// <typeparam name="T2">目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <param name="target">目标对象</param>
        public static void Cast<T1, T2>(T1 source, ref T2 target)
        {
            try
            {
                var sourceProperties = source.GetType().GetProperties();
                var targetProperties = target.GetType().GetProperties();

                foreach (var sourceProperty in sourceProperties)
                {
                    foreach (var targetProperty in targetProperties)
                    {
                        bool sameName = sourceProperty.Name == targetProperty.Name;
                        bool sameType = sourceProperty.PropertyType == targetProperty.PropertyType;

                        if (sameName && sameType)
                        {
                            targetProperty.SetValue(target, sourceProperty.GetValue(source));
                        }
                    }
                }
            }
            catch
            {
#if DEBUG
                Debugger.Break();
#endif
                throw;
            }
        }
    }
}